using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace Dida
{
	/// <summary>
	/// A static class with methods that operate on files and/or file names.
	/// </summary>
	public static class FileUtils
	{
		[Flags]
		public enum NormalizeFlags
		{
			None = 0,
			KeepCase = (1 << 0),
		}

		public static string NormalizeFileName(string fileName,string path,NormalizeFlags flags,
			char pathSepChar,char altPathSepChar,char volumeSepChar)
		{
			// some fancy heuristics
			bool useVolume = volumeSepChar != pathSepChar && volumeSepChar != altPathSepChar;

			fileName = fileName.Trim();
			if (fileName.Length == 0)
				throw new Exception(fileName + " isn't a valid file name");

			char[] pathSeparators = new char[] { pathSepChar, altPathSepChar };

			List<string> fileNameParts = new List<string>(
				fileName.Split(pathSeparators));
			fileNameParts = RemoveEmptyStrs(fileNameParts);

			List<string> curPathParts = new List<string>(
				path.Split(pathSeparators));
			curPathParts = RemoveEmptyStrs(curPathParts);

			if (useVolume)
			{
				// check if the first item is a drive letter
				if (fileNameParts[0].Length == 2 && fileNameParts[0][1] == volumeSepChar)
				{
					curPathParts.Clear();
				}
				// if it starts with a /, it will start at the root of the current drive
				// (but it won't change the drive)
				else if (fileName[0] == pathSepChar ||
					fileName[0] == altPathSepChar)
				{
					// The first part should be the drive letter, so that should be
					// kept. The rest can be discarded.
					curPathParts.RemoveRange(1, curPathParts.Count - 1);
				}
				// else it's a completely relative path
			}
			else
			{
				if (fileName[0] == pathSepChar || fileName[0] == altPathSepChar)
				{
					curPathParts.Clear();
				}
			}

			for (int i = 0; i < fileNameParts.Count - 1; i++)
			{
				fileNameParts[i] = fileNameParts[i].Trim();
				if (fileNameParts[i] == ".")
					continue;
				else if (fileNameParts[i] == "..")
				{
					curPathParts.RemoveAt(curPathParts.Count - 1);
				}
				else
				{
					curPathParts.Add(fileNameParts[i]);
				}
			}

			// reconstruct the path, with a standard naming convention
			StringBuilder ret = new StringBuilder();
			if (!useVolume)
				ret.Append(pathSepChar);
			for(int i = 0;i < curPathParts.Count;i++)
			{
				if((flags & NormalizeFlags.KeepCase) != 0)
					ret.Append(curPathParts[i]);
				else
					ret.Append(curPathParts[i].ToLower());

				ret.Append(pathSepChar);
			}

			if ((flags & NormalizeFlags.KeepCase) != 0)
			{
				ret.Append(fileNameParts[fileNameParts.Count - 1]);
			}
			else
			{
				ret.Append(fileNameParts[fileNameParts.Count - 1].ToLower());
			}

			return ret.ToString();
		}

		private static List<string> RemoveEmptyStrs(List<string> src)
		{
			List<string> ret = new List<string>();
			foreach (string s in src)
			{
				if (s != string.Empty)
				{
					ret.Add(s);
				}
			}
			return ret;
		}

		public static string NormalizeFileName(string fileName,string path,NormalizeFlags flags)
		{
			return NormalizeFileName(fileName, path, flags, Path.DirectorySeparatorChar,
				Path.AltDirectorySeparatorChar,Path.VolumeSeparatorChar);
		}

		public static string NormalizeFileName(string fileName, string path)
		{
			NormalizeFlags flags = NormalizeFlags.None;
			// I'm sure there must be a better way, but I couldn't find it.
			if(Environment.OSVersion.Platform == PlatformID.Unix)
				flags |= NormalizeFlags.KeepCase;
			return NormalizeFileName(fileName, path, flags);
		}

		public static string NormalizeFileName(string fileName)
		{
			NormalizeFlags flags = NormalizeFlags.None;
			// I'm sure there must be a better way, but I couldn't find it.
			if (Environment.OSVersion.Platform == PlatformID.Unix)
				flags |= NormalizeFlags.KeepCase;
			return NormalizeFileName(fileName, Environment.CurrentDirectory, flags);
		}
	}
}